import * as path from 'path';
import * as os from 'os';
import *  as fs from 'fs-extra';

import {App, normalizePath, Notice} from 'obsidian';
import decompress from 'decompress';
import * as parse5 from "parse5"
import { Flomo } from './flomo';
import { generateMoments } from './obIntegration/moments';
import { generateCanvas } from './obIntegration/canvas';
import {stripLeft, stripLefts, stripRight} from "./utils";
import {start} from "repl";
import Path from "path";
import GenerateHeadings from "./generateHeadings";

const FLOMO_CACHE_LOC = path.join(os.homedir(), ".flomo/cache/");
const featureTagList = [
    { tag: "#待办提醒/待办", prefix: () => {return "- [ ]";} },
    { tag: "#待办提醒/已完成", prefix: () => {return "- [x]";} },
    { tag: "#待办提醒/意向中", prefix: () => {return "- [ ]";} },
    { tag: "#待办提醒", prefix: () => {return "- [ ]";} },
];
const wrapFeatureTagPrefix = (memoContent: string, defaultPrefix: string) => {
    const matches = featureTagList.filter(t => memoContent.indexOf(t.tag) !== -1); // memo中包含"#待办提醒/待办"的tag
    if (matches.length > 0) {
        if (matches[0]) {
            memoContent = memoContent.replace(matches[0].tag, "");
            return `${matches[0].prefix()} ${memoContent}`;
        }
    } else {
        return `${defaultPrefix} ${memoContent}`;
    }
}
const dispatchTagList= [ // 有顺序影响的，长的应该在前面
  { tag: "#日常笔记/项目运营/交银保险资管工作", heading: "#### 交银保险资管工作", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, "*");}},
  { tag: "#日常笔记/IT知识学习", heading: "### IT知识学习", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, "*");} },
  { tag: "#日常笔记/IT开发笔记", heading: "### IT开发笔记", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, "*");} },
  { tag: "#日常笔记/游戏影音娱乐", heading: "### 游戏影音娱乐", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, "*");} },
  { tag: "#日常笔记/财经投资业务知识学习", heading: "### 财经投资业务知识学习", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, "*");} },
  { tag: "#日常笔记/项目运营", heading: "### 项目运营", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, "*");} },
  { tag: "#日常笔记", heading: "## ✒️日常笔记", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, "*");} },
  { tag: "#思考总结", heading: "## ✏思考总结", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, `* ${memoTime.split(":").slice(0, 2).join(":")}`);} },
];

class TemplateString extends String {
    interpolate(params: Object) {
        const names = Object.keys(params);
        const vals = Object.values(params);
        return new Function(...names, `return \`${this}\`;`)(...vals);
    }
}

export class FlomoImporter {
    private config: Record<string, any>;
    private app: App;
    private count: number;
    private skippedCount: number;

    constructor(app: App, config: Record<string, string>) {
        this.config = config;
        this.app = app;
        this.count = 0;
        this.skippedCount = 0;
        //@ts-ignore
        this.config["baseDir"] = app.vault.adapter.basePath;
    }

    private async sanitize(path: string): Promise<string> {
        const flomoData = await fs.readFile(path, "utf8");
        const document = parse5.parse(flomoData);
        return parse5.serialize(document);
    }

    private cleanupMemoContent(memoContent) {
        return memoContent.replaceAll("\n", " ").replaceAll("\r", " ");
    }

    private processUrl(memoContent) {
        return memoContent.replace(/(https?:\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|])/g, '[原文]($1)');
    }

    private appendTag(memoContent, tag) {
        if (tag && tag.trim() !== "") {
            return `${memoContent} #${tag}`;
        } else {
            return memoContent;
        }
    }

    private dispatch(tags, memo) {
        if (tags) {
            let memoContent;
            if (tags.includes("#多行")) {
                memoContent = memo.content.replace("#多行", "");
            } else {
                memoContent = this.cleanupMemoContent(memo.content);
            }
            memoContent = this.processUrl(memoContent);
            for (const tag of tags) {
                // JM: 只要当前memo的tag存在于dispatchTagList中，也可能是多个。但不用全匹配，只要是#待办开头的都算
                // const matches = dispatchTagList.filter(t => t.tag.startsWith(tag));
                const matches = dispatchTagList.filter(t => tag.startsWith(t.tag)); // #日常笔记/工作.startsWith(#日常笔记)
                if (matches.length > 0) {
                    if (matches[0]) {
                        memoContent = memoContent.replace(tag, "");
                        const subTag = stripLeft(stripLefts(tag, matches[0].tag), "/");
                        return {
                            heading: matches[0].heading,
                            // memoContent: `${matches[0].prefix(memo)} ${memoContent}`, // 普通的tag在memoContent中仍然是保留的
                            memoContent: matches[0].postProcess(memoContent, memo.time), // 普通的tag在memoContent中仍然是保留的
                            tag: subTag, // 这里一条memo返回一个tag是合理的，因为在这里只能处理特定的分类标签的子标签，而分类标签一定是只有一个。
                        };
                    }
                }
            }
        }
        return {
            heading: null,
            memoContent: null,
            tag: null,
        }
        // return {
        //     heading: "## ✏思考总结",
        //     memoContent: `* ${memo.time.split(":").slice(0, 2).join(":")} ${memoContent}`,
        //     tag: "",
        // }
    }

    private static findOffset(heading, content) {
        const regex = new RegExp(`(?<=[\\n\\r])${heading}(?=[\\n\\r]|$)`);
        const match = regex.exec(content);
        if (match) {
            return {startOffset: match.index, endOffset: match.index + heading.length};
        }
        else {
            console.log(`not found heading ${heading} in: \n ${content}`);
            return null
        }
    }

    private async readImportedMemos(filepath) {
        const file = this.app.vault.getFileByPath(filepath);
        if (await this.app.vault.adapter.exists(filepath)){
            const content = await this.app.vault.read(file);
            const lines = content.trim().split('\n');
            const map = new Map();
            for (const line of lines) {
                const [id, name] = line.trim().split('->').map(item => item.trim());
                if (id && name) {
                    map.set(id, name);
                }
            }
            return map;
        } else {
            return new Map();
        }
    }

    private async writeImportedMemos(map, filepath) {
        const lines = [];
        for (const [id, name] of map.entries()) {
            lines.push(`${id} -> ${name}`);
        }
        // 将数组中的每一行连接成一个文本内容
        const fileContent = lines.join('\n');
        await this.app.vault.adapter.write(
          filepath,
          fileContent
        );
    }

    private getAttachmentFolderPath(mdFileName: string) {
        let path = new TemplateString('./attachments/${filename}').interpolate({
            filename: mdFileName
        });
        return path;
    }

    private async importMemos(flomo: Flomo, tmpDir: string, files: decompress.File[]): Promise<Flomo> {

        // const allowBilink: boolean = this.config["expOptionAllowbilink"];
        // const margeByDate: boolean = this.config["mergeByDate"];
        // @ts-ignore
        // const filesToWrite: [{filePath: string,  fileContent: string}] = [];
        const filesToWrite = new Map();
        let fileContent = "";
        let prevDate = "";
        const checksumFilePath = this.config["checksumFilePath"].trim();
        let checksumMemos = await this.readImportedMemos(checksumFilePath);
        for (const [idx, memo] of flomo.memos.entries()) {
            try {
                console.log(`processing ${memo.title}`)
                if (checksumMemos.has(memo.datetime)) {
                    this.skippedCount += 1;
                    continue;
                }

                const regex = /#[^\s]+/g;
                const tags = memo.content.match(regex);
                let {heading, memoContent, tag} = this.dispatch(tags, memo);

                if (heading === null) {
                    continue;
                }
                const file = this.app.metadataCache.getFirstLinkpathDest(
                  memo.date,
                  ''
                );
                let mdFolderPath: string = Path.dirname(file.path);
                const attachmentFolderPath = Path.join(mdFolderPath, this.getAttachmentFolderPath(file.basename));
                const decompressRootFolder = stripRight(files[0].path, '/');
                for (const f of files) {
                    // TODO: 这里只要当天有笔记可以被dispath，那么这天其他笔记中的图片就会被复制过来，算是一个bug吧
                    // if (f.type == "directory" && f.path.endsWith(`/file/${file.basename}/`)) {
                    if (f.type === "file" && f.path.startsWith(`${decompressRootFolder}/file/${file.basename}/`)) {
                        console.debug(`DEBUG: copying from ${tmpDir}/${f.path} to ${this.config["baseDir"]}/${attachmentFolderPath}`)
                        if (!await this.app.vault.adapter.exists(attachmentFolderPath))
                            await this.app.vault.adapter.mkdir(attachmentFolderPath);
                        await fs.copy(normalizePath(Path.join(tmpDir, f.path)), normalizePath(Path.join(this.config["baseDir"], attachmentFolderPath, Path.basename(f.path))));
                        break;
                    }
                }

                if (prevDate !== memo.date) {
                    fileContent = await this.app.vault.read(file);
                    prevDate = memo.date;
                }

                if (!FlomoImporter.findOffset(heading, fileContent)) {
                    const vaultStartLocation = "1_Inbox/Diary";
                    const ingoredHeaders = ["## 🕓今日事项", "## ✒️日常笔记", "## 🔔待办提醒", "## ✏思考总结"];
                    const ignoredSuffixArray = [".excalidraw.md"];
                    // 如果没找到heading，会报错并且跳过这条memo
                    let generateHeadings = new GenerateHeadings(this.app, vaultStartLocation, ingoredHeaders, ignoredSuffixArray);
                    fileContent = generateHeadings.selectSuggestion(heading, fileContent);  // heading = "#### 交银保险资管工作"
                }

                let position = FlomoImporter.findOffset(heading, fileContent);
                const startOffset = position.endOffset;
                fileContent = fileContent.slice(0, startOffset).trimEnd() + `\n${this.appendTag(memoContent.trim(), tag)}\n` + fileContent.slice(startOffset).trimStart();
                filesToWrite.set(file.path, fileContent);
                checksumMemos.set(memo.datetime, this.cleanupMemoContent(memo.content));
                this.count += 1;
            } catch (err) {
                console.error(err);
                new Notice(`memo import error, skipped! Details:\n${err}`);
                this.skippedCount += 1;
            }

        }
        await this.writeImportedMemos(checksumMemos, checksumFilePath);

        for (const [key, value] of filesToWrite) {
            await this.app.vault.adapter.write(
              key,
              value
            );
        }

        new Notice(`Imported ${this.count} memos, skipped ${this.skippedCount} memos`)
        return flomo;
    }

    async import(): Promise<Flomo> {

        // 1. create workspace
        const tmpDir = path.join(FLOMO_CACHE_LOC, "data")
        await fs.mkdirp(tmpDir);

        // 2. unzip flomo_backup.zip to workspace
        const files = await decompress(this.config["rawDir"], tmpDir)

        // 3. copy attachments to ObVault
        // const obVaultConfig = await fs.readJson(`${this.config["baseDir"]}/${this.app.vault.configDir}/app.json`)
        // const attachementDir = obVaultConfig["attachmentFolderPath"] + "/flomo/";
        //
        // for (const f of files) {
        //     if (f.type == "directory" && f.path.endsWith("/file/")) {
        //         console.debug(`DEBUG: copying from ${tmpDir}/${f.path} to ${this.config["baseDir"]}/${attachementDir}`)
        //         await fs.copy(`${tmpDir}/${f.path}`, `${this.config["baseDir"]}/${attachementDir}`);
        //         break
        //     }
        //
        // }

        // 4. import Memos
        const backupData = await this.sanitize(`${tmpDir}/${files[0].path}/${this.config["indexFilename"]}`);
        const startDate =this.config.hasOwnProperty("startDate") && this.config["startDate"] !== "" ? new Date(this.config["startDate"]): null;
        const endDate = this.config.hasOwnProperty("endDate") && this.config["endDate"] !== "" ? new Date(this.config["endDate"]): null;
        const flomo = new Flomo(backupData, startDate, endDate);

        const memos = await this.importMemos(flomo, tmpDir, files);

        // // 5. Ob Intergations
        // // If Generate Moments
        // if (this.config["optionsMoments"] != "skip") {
        //     await generateMoments(app, memos, this.config);
        // }
        //
        // // If Generate Canvas
        // if (this.config["optionsCanvas"] != "skip") {
        //     await generateCanvas(app, memos, this.config);
        // }

        // 6. Cleanup Workspace
        await fs.remove(tmpDir);

        // 7. Save batch process progress
        // const filePath = "0_Workshop/importer/batch.md";
        // const fileContent = "2024-01-01";
        // await this.app.vault.adapter.write(
        //   filePath,
        //   fileContent
        // );

        return flomo

    }

}
